using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Aima.Core.Util.math
{
/**
 * see "http://demonstrations.wolfram.com/MixedRadixNumberRepresentations/"
 * Mixed Radix Number Representations 
 */

    public class MixedRadixNumber //: Number 
    {
       
        private readonly int[] _radices;
        private int[] _currentNumeralValue;
        private long _maxValue;
        private bool _recalculate = true;
        private long _value;

        /// <summary>
        /// Constructs a mixed radix number with a specified value and a specified array of radices
        /// </summary>
        /// <param name="value">the value of the mixed radix number</param>
        /// <param name="radices">the radices used to represent the value of the mixed radix</param>
        public MixedRadixNumber(long value, int[] radices)
        {
            _value = value;
            _radices = new int[radices.Length];
            Array.Copy(radices, 0, _radices, 0, radices.Length);
            CalculateMaxValue();
        }

        // Constructs a mixed radix number with a specified value and a specified list of radices.
	    public MixedRadixNumber(long value, List<int> radices)
        {
            _value = value;
            _radices = new int[radices.Count];
            for (int i = 0; i < radices.Count; i++)
            {
                _radices[i] = radices[i];
            }
            CalculateMaxValue();
        }

        // Constructs a mixed radix number with a specified array of numerals and a specified array of radices.
	    public MixedRadixNumber(int[] radixValues, int[] radices)
        {
            _radices = radices;
            //TODO: no Number base class --> this(0, radices);
            SetCurrentValueFor(radixValues);
        }

        // Returns the value of the mixed radix number with the specified array of  numerals and the current array of radices.
	    public long GetCurrentValueFor(int[] radixValues)
        {
            if (radixValues.Length != _radices.Length)
            {
                throw new ArgumentException("Radix values not same size as Radices.");
            }

            long cvalue = 0;
            long mvalue = 1;
            for (int i = 0; i < radixValues.Length; i++)
            {
                if (radixValues[i] < 0 || radixValues[i] > _radices[i])
                {
                    throw new ArgumentException("Radix value " + i + " is out of range for radix at this position");
                }
                if (i > 0)
                {
                    mvalue *= _radices[i - 1];
                }
                cvalue += mvalue*radixValues[i];
            }
            return cvalue;
        }

        // Sets the value of the mixed radix number with the specified array of numerals and the current array of radices.
        public void SetCurrentValueFor(int[] radixValues)
        {
            _value = GetCurrentValueFor(radixValues);
            Array.Copy(radixValues, 0, _currentNumeralValue, 0, radixValues.Length);
            _recalculate = false;
        }

       // Returns the maximum value which can be represented by the current array of radices.
	
        public long MaxAllowedValue
        {
            get {return _maxValue;}
        }

        //Increments the value of the mixed radix number, if the value is less than
	    // the maximum value which can be represented by the current array of radices.
        public bool Increment()
        {
            if (_value < _maxValue)
            {
                _value++;
                _recalculate = true;
                return true;
            }

            return false;
        }

        // Decrements the value of the mixed radix number, if the value is greater than zero.
        public bool Decrement()
        {
            if (_value > 0)
            {
                _value--;
                _recalculate = true;
                return true;
            }
            return false;
        }

        // Returns the numeral at the specified position.
        public int GetCurrentNumeralValue(int atPosition)
        {
            if (atPosition >= 0 && atPosition < _radices.Length)
            {
                if (_recalculate)
                {
                    long quotient = _value;
                    for (int i = 0; i < _radices.Length; i++)
                    {
                        if (0 != quotient)
                        {
                            _currentNumeralValue[i] = (int) quotient%_radices[i];
                            quotient = quotient/_radices[i];
                        }
                        else
                        {
                            _currentNumeralValue[i] = 0;
                        }
                    }
                    _recalculate = false;
                }
                return _currentNumeralValue[atPosition];
            }
            throw new ArgumentException(
                "Argument atPosition must be >=0 and < " + _radices.Length);
        }

        
        // START-Number
        //override
        public int IntValue()
        {
            return (int) LongValue();
        }

        //override
        public long LongValue()
        {
            return _value;
        }

        //override
        public float FloatValue()
        {
            return LongValue();
        }

        //override
        public double DoubleValue()
        {
            return LongValue();
        }

        // END-Number
        //

        public
            override String ToString()
        {
            var sb = new StringBuilder();

            for (int i = 0; i < _radices.Length; i++)
            {
                sb.Append("[");
                sb.Append(GetCurrentNumeralValue(i));
                sb.Append("]");
            }

            return sb.ToString();
        }

        // Sets the maximum value which can be represented by the current array of radices.
	    private void CalculateMaxValue()
        {
            if (0 == _radices.Length)
            {
                throw new ArgumentException(
                    "At least 1 radix must be defined.");
            }
            if (_radices.Any(t => t < 2))
            {
                throw new ArgumentException(
                    "Invalid radix, must be >= 2");
            }

            // Calculate the maxValue allowed
            _maxValue = _radices[0];
            for (int i = 1; i < _radices.Length; i++)
            {
                _maxValue *= _radices[i];
            }
            _maxValue -= 1;

            if (_value > _maxValue)
            {
                throw new ArgumentException(
                    "The value ["
                    + _value
                    + "] cannot be represented with the radices provided, max value is "
                    + _maxValue);
            }

            _currentNumeralValue = new int[_radices.Length];
        }
    }
}